知识点名称:Handler的使用

编号: K10-2

前驱知识点编号:K10-1,K3-1

作者:

讲义内容:

1 .Handler的基本概念

在android里,Handler负责发送和处理消息,通过它还可以实现其他线程与Main线程之间的消息通讯。另外,handler还可以可以分发Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程), 也就是说Handler对象初始化后,就默认与对它初始化的进程的消息队列绑定,因此可以利用Handler所包含的消息队列,制定一些操作的顺序。

我们先来看Handler分发Message和Runnable的常用方法:

post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable,long)

以上post类方法允许排列一个Runnbale对象到主线程对列中。

sendMessage(Message)
sendMessageAtTime (Message)
sendMessageDelayed (Message)
sendEmptyMessage(Message)

以上sendMessage类方法允许排列一个带数据的Message对象到MessageQueue中,等待处理。

下面通过具体的实例演示Handler的基本用法以及它是如何实现多线程之间的通讯的。

  1. Handler的基本用法

对于耗时操作我们会放到其他的线程中处理,多线程可以通过implements Runnable 或 extends Thread的方式实现,在此我们选择前者。多线程的核心机制就是Handler,Handler会绑定一个线程(该线程是创建Handler实例的线程,通常是主线程),进而与此线程的Looper对象和MessageQueue对象绑定,Looper负责管理线程的消息队列和消息循环,也就是它负责不停的循环的遍历队列MessageQueue,从中取出符合条件的Message,然后交给与MessageQueue绑定的Handler处理。MessageQueue是存放Message的队列,通过Handler的sendMessage方法或者Message的sendToTarget发送到MessageQueue中,而Message是线程间通讯的消息载体。

下面我们通过具体实例加以说明。本实例实现的是当开始按钮按下时,会启动一个线程,并绑定到handler中,该线程发送带有参数的message到handler的消息队列中,消息队列的另一端获取该消息,并且用该消息的参数来更新进度条,这就要用到handler的handleMessage来处理消息,处理方法是获得消息队列中的消息参数,用这些参数来完成对进度条的更新,因此需要在程序中重写handleMessage方法。

界面布局的核心代码如下:

<ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />
    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="显示进度" />

界面交互的核心代码如下:

public class MainActivity extends ActionBarActivity {

     private ProgressBar bar;
     private Button bt1;     
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main); 
        bar=(ProgressBar) this.findViewById(R.id.progressBar1);
        bt1=(Button) this.findViewById(R.id.button1);

        bt1.setOnClickListener(new ButtonListener());

    }  
    class ButtonListener implements OnClickListener
    {

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            //进度条设置成可见状态
            bar.setVisibility(View.VISIBLE);
            //把线程加入到线程队列中
            handler.post(runnable);
        }

    } 
    //匿名内部类,用来复写Handler中的handleMessage方法
    Handler  handler =new Handler(){
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            bar.setProgress(msg.arg1);
            handler.post(runnable);
        }

    };
    //将要执行的操作写在线程对象的run方法中
    Runnable runnable=new Runnable()
    {
        int i=0;
        public void run() {
            // TODO Auto-generated method stub
            System.out.println("begin thread");
            i=i+20;
            //得到一个消息队列,Message由android系统提供
            //handler会用到两个队列,一个是线程队列,一个是消息队列
            Message msg=handler.obtainMessage();
        //将msg的arg1设置成i,还有一个arg2这个参数,用这两个参数传递消息
            //有点是系统系统消耗较少
            msg.arg1=i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //将消息加入到消息队列中
            handler.sendMessage(msg);
            if(i==100)
            {
                //将线程对象从handler中移除
                handler.removeCallbacks(runnable);
            }
        }
    };
}

本例中,在main线程中创建了Handler对象,并重写了其handleMessage方法,用于对消息的处理。由于是在main线程中创建的Handler对象,因此Handler对象会绑定当前的main线程,并与当前activiy的Looper对象和MessageQueue对象关联。当点击按钮时,首先让进度条可见,并通过handler.post(runnable)语句将线程加入到线程队列中,进而直接调用runnable里面的run方法,这里要注意的是并没有开启新的线程,而是在当前的main线程中直接执行了run方法。在run方法中,使用handler.obtainMessage()语句,让Message对象与当前的Handler对象绑定,这样Message对象就由Handler对象发送和接收处理,由Looper负责从MessageQueue中取出Message交给Handler的handleMessage方法处理。

  1. Handler和多线程

Handler的post方法只负责将线程加入到线程队列中,并直接调用了线程的run方法,并没有开启新的线程,我们首先通过实例来验证,该实例的界面上包含一个按钮:

public class MainActivity extends ActionBarActivity {
     Handler handler=new Handler();
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        handler.post(r);
        setContentView(R.layout.activity_main); 
        System.out.println("activity--"+Thread.currentThread().getId());
System.out.println("activityname---"+Thread.currentThread().getName());

    }  
    //将要执行的操作写在线程对象的run方法中
    Runnable r=new Runnable()
    {
    public void run() {
            // TODO Auto-generated method stub
        System.out.println("handler--"+Thread.currentThread().getId());
            System.out.println("handlername---"+Thread.currentThread().getName());    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    };   
}

运行此程序会发现需要等待10秒才会显示该按钮,由于首先执行 handler.post(r),就会先执行r的run()方法,在run()方法设置了延迟10秒。另外从下面的输出截图也可以看出main线程和r线程中打印出的线程的ID和线程的名字都是一样的,运行结果如下。

上例说明整个run中的操作和主线程处于同一个线程,那么如果run中的操作是比较耗时的,就会导致“假死”现象。那么,如何解决这种问题,也就是让handler对象绑定到处于另外一个线程的消息队列中,它们将在另外的消息队列中被处理,而不影响主线程,即通过异步解决耗时操作,下节我们引入HandlerThread来开启新的线程。

4 .HandlerThread的使用

HandlerThread可以将获取到的Looper传递给Handler对象,即由处于另外线程的Looper代替handler初始化时默认绑定的消息队列来处理消息。HandlerThread可以处理消息循环的线程,它是一个拥有Looper的线程,可以处理消息循环; 其实与其说Handler和一个线程绑定,倒不如说Handler和Looper是对应的。我们将10.2.3节中的例题加以改进,其代码如下:

public class MainActivity extends ActionBarActivity {

   public void onCreate(Bundle savedInstanceState)  
   {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.activity_main); 
       //打印当前线程的ID和名字
       System.out.println("activity--"+Thread.currentThread().getId());
       System.out.println("activityname---"+Thread.currentThread().getName());
       //生成一个HandlerThread对象
       HandlerThread thread=new  HandlerThread("handlerthread");
       //在使用HandlerThread的getLooper()方法之前,必须先调用该类的start(),同时开启一个新线程;  
       thread.start();
       //创建一个Handler对象,并使用HandlerThread的Looper
       MyHandler handler=new MyHandler(thread.getLooper());
       handler.post(r);
   }  

   class MyHandler extends Handler{
        public MyHandler(Looper looper) {
            super(looper);
        } 
   };
   //将要执行的操作写在线程对象的run方法中
   Runnable r=new Runnable()
   {
    public void run() {
            // TODO Auto-generated method stub
            System.out.println("handler--"+Thread.currentThread().getId());
            System.out.println("handlername---"+Thread.currentThread().getName());    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
   };   
}

运行结果截图如下:

从图中可以看出打印的线程的ID和名字不再一样了,说明通过HandlerThread可以开启一个新的线程,实现耗时操作的异步执行。该例中至少含有两个线程,即主线程和新开启的线程。下面我们再对该例加以改进,来实现线程间的通讯,通讯内容是一个人的姓名和年龄,代码如下:

public class MainActivity extends ActionBarActivity {
    private Message msg;
   public void onCreate(Bundle savedInstanceState)  
   {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.activity_main); 
       //打印当前线程的ID和名字
       System.out.println("activity--"+Thread.currentThread().getId());
       System.out.println("activityname---"+Thread.currentThread().getName());
       //生成一个HandlerThread对象
       HandlerThread thread=new  HandlerThread("handlerthread");
       //在使用HandlerThread的getLooper()方法之前,必须先调用该类的start(),同时开启一个新线程;  
       thread.start();
       //创建一个Handler对象,并使用HandlerThread的Looper
       MyHandler handler=new MyHandler(thread.getLooper());
       handler.post(r);
        msg=handler.obtainMessage();
       //创建Bundle对象,并将要传递的数据进行封装
       Bundle bundle=new Bundle();
       bundle.putString("name", "zhangsan");
       bundle.putInt("age", 18);
       msg.setData(bundle);
       //将msg发送到handler
       msg.sendToTarget();
   }  

   class MyHandler extends Handler{
        public MyHandler(Looper looper) {
            super(looper);
        } 
   };
   //将要执行的操作写在线程对象的run方法中
   Runnable r=new Runnable()
   {
    public void run() {
            // TODO Auto-generated method stub
            System.out.println("handler--"+Thread.currentThread().getId());
            System.out.println("handlername---"+Thread.currentThread().getName());    
            Bundle bundle=msg.getData();
            String name=bundle.getString("name");
            int age=bundle.getInt("age");
            System.out.println("name="+name+"  age="+age);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
   };   
}

运行结果截图如下:

results matching ""

    No results matching ""